package net.unicon.cas.mfa.authentication; import net.unicon.cas.mfa.web.support.MultiFactorAuthenticationSupportingWebApplicationService; import net.unicon.cas.mfa.web.support.MultiFactorWebApplicationServiceFactory; import org.apache.commons.lang3.builder.ToStringBuilder; import org.jasig.cas.authentication.Authentication; import org.jasig.cas.authentication.principal.Response.ResponseType; import org.jasig.cas.authentication.principal.WebApplicationService; import org.jasig.cas.services.RegisteredService; import org.jasig.cas.services.ServicesManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** * Resolves potential mfa request based on the configured principal attribute and the service attribute. * If a service has an mfa-role attribute, then its authn_method is only enforce if the principal matches the requested attribute. * * @author John Gasper * @author Unicon, inc. */ public class DefaultRegisteredServiceMfaRoleProcessorImpl implements RegisteredServiceMfaRoleProcessor { /** * The logger. */ protected final Logger logger = LoggerFactory.getLogger(this.getClass()); /** * Mfa service factory. */ private final MultiFactorWebApplicationServiceFactory mfaServiceFactory; /** * Authentication Method Configuration Provider. */ private final AuthenticationMethodConfigurationProvider authenticationMethodConfiguration; /** * used to cache regex patterns for faster lookup/execution. */ private final Map<String, Pattern> patternCache; private final Object cacheLock = new Object(); /** * the services manager. */ private final ServicesManager servicesManager; /** * Ctor. * * @param mfaServiceFactory mfaServiceFactory * @param authenticationMethodConfiguration the authentication method loader * @param servicesManager a CAS Service Manager instance */ public DefaultRegisteredServiceMfaRoleProcessorImpl( final MultiFactorWebApplicationServiceFactory mfaServiceFactory, final AuthenticationMethodConfigurationProvider authenticationMethodConfiguration, final ServicesManager servicesManager) { this.mfaServiceFactory = mfaServiceFactory; this.authenticationMethodConfiguration = authenticationMethodConfiguration; this.servicesManager = servicesManager; this.patternCache = new ConcurrentHashMap<>(); } /** * Resolves the authn_method for a given service if it supports mfa_role and the user has the appropriate attribute. * * @param authentication the user authentication object * @param targetService the target service being tested * @return a list (usually one) mfa authn request context. */ public List<MultiFactorAuthenticationRequestContext> resolve(@NotNull final Authentication authentication, @NotNull final WebApplicationService targetService) { String authenticationMethodAttributeName = null; final List<MultiFactorAuthenticationRequestContext> list = new ArrayList<>(); if (authentication != null && targetService != null) { final ServiceMfaData serviceMfaData = getServicesAuthenticationData(targetService); if (serviceMfaData == null || !serviceMfaData.isValid()) { logger.debug("No specific mfa role service attributes found"); return list; } logger.debug("Found MFA Role: {}", serviceMfaData); authenticationMethodAttributeName = serviceMfaData.attributeName; final Object mfaAttributeValueAsObject = authentication.getPrincipal().getAttributes().get(serviceMfaData.attributeName); if (mfaAttributeValueAsObject != null) { if (mfaAttributeValueAsObject instanceof String) { final String mfaAttributeValue = mfaAttributeValueAsObject.toString(); final MultiFactorAuthenticationRequestContext ctx = getMfaRequestContext( serviceMfaData, mfaAttributeValue, targetService); if (ctx != null) { list.add(ctx); } } else if (mfaAttributeValueAsObject instanceof List) { final List<String> mfaAttributeValues = (List<String>) mfaAttributeValueAsObject; for (final String mfaAttributeValue : mfaAttributeValues) { final MultiFactorAuthenticationRequestContext ctx = getMfaRequestContext( serviceMfaData, mfaAttributeValue, targetService); if (ctx != null) { list.add(ctx); } } } else if (mfaAttributeValueAsObject instanceof Collection) { final Collection mfaAttributeValues = (Collection) mfaAttributeValueAsObject; for (final Object mfaAttributeValue : mfaAttributeValues) { final MultiFactorAuthenticationRequestContext ctx = getMfaRequestContext( serviceMfaData, String.valueOf(mfaAttributeValue), targetService); if (ctx != null) { list.add(ctx); } } } else { logger.debug("No MFA attribute found."); } } } if (list.isEmpty()) { logger.debug("No multifactor authentication requests could be resolved based on [{}].", authenticationMethodAttributeName); return null; } return list; } /** * Gets mfa request context. * * @param serviceMfaData service specific mfa settings * @param attributeValue the value found in the attribute * @param targetService the target service * @return the mfa request context */ private MultiFactorAuthenticationRequestContext getMfaRequestContext(final ServiceMfaData serviceMfaData, final String attributeValue, final WebApplicationService targetService) { final RegisteredService registeredService = this.servicesManager.findServiceBy(targetService); String method = null; if (registeredService.getProperties().containsKey("method")) { method = registeredService.getProperties().get("method").getValue(); } if (match(serviceMfaData.getAttributePattern(), attributeValue)) { if (!this.authenticationMethodConfiguration.containsAuthenticationMethod(serviceMfaData.getAuthenticationMethod())) { logger.info("MFA attribute [{}] with value [{}] is not supported by the authentication method configuration.", serviceMfaData.getAttributeName(), serviceMfaData.getAuthenticationMethod()); return null; } final int mfaMethodRank = this.authenticationMethodConfiguration.getAuthenticationMethod( serviceMfaData.getAuthenticationMethod()).getRank(); final MultiFactorAuthenticationSupportingWebApplicationService svc = this.mfaServiceFactory.create(targetService.getId(), targetService.getId(), targetService.getArtifactId(), "POST".equals(method) ? ResponseType.POST : ResponseType.REDIRECT, serviceMfaData.getAuthenticationMethod(), MultiFactorAuthenticationSupportingWebApplicationService.AuthenticationMethodSource.PRINCIPAL_ATTRIBUTE); return new MultiFactorAuthenticationRequestContext(svc, mfaMethodRank); } logger.trace("{} did not match {}", attributeValue, serviceMfaData.getAttributePattern()); return null; } /** * Match will compare the value to the pattern. * * @param attributePattern the pattern to check * @param attributeValue the value to check * @return true if a match is found. otherwise false */ private boolean match(final String attributePattern, final String attributeValue) { Pattern pattern; synchronized (cacheLock) { pattern = patternCache.get(attributePattern); if (pattern == null) { pattern = Pattern.compile(attributePattern); patternCache.put(attributePattern, pattern); } } return pattern.matcher(attributeValue).matches(); } /** * Looks up the mfa data for a specific service. * * @param targetService the service to check * @return service specific mfa settings */ private ServiceMfaData getServicesAuthenticationData(final WebApplicationService targetService) { final RegisteredService registeredService = this.servicesManager.findServiceBy(targetService); if (registeredService == null) { logger.debug("No registered service is found. Delegating to the next argument extractor in the chain..."); return null; } final ServiceMfaData serviceData = new ServiceMfaData(); if (registeredService.getProperties().containsKey(RegisteredServiceMfaRoleProcessor.MFA_ATTRIBUTE_NAME)) { serviceData.setAttributeName(registeredService.getProperties() .get(RegisteredServiceMfaRoleProcessor.MFA_ATTRIBUTE_NAME).getValue()); } if (registeredService.getProperties().containsKey(RegisteredServiceMfaRoleProcessor.MFA_ATTRIBUTE_PATTERN)) { serviceData.setAttributePattern(registeredService.getProperties(). get(RegisteredServiceMfaRoleProcessor.MFA_ATTRIBUTE_PATTERN).getValue()); } if (registeredService.getProperties().containsKey(MultiFactorAuthenticationRequestResolver.DEFAULT_MFA_METHOD_ATTRIBUTE_NAME)) { serviceData.setAuthenticationMethod(registeredService.getProperties(). get(MultiFactorAuthenticationRequestResolver.DEFAULT_MFA_METHOD_ATTRIBUTE_NAME).getValue()); } return serviceData; } /** * A POJO to store data retrieved from the Services Manager. */ private class ServiceMfaData { public static final String CANNOT_BE_NULL = "{} cannot be null"; private String authenticationMethod; private String attributeName; private String attributePattern; public String getAuthenticationMethod() { return authenticationMethod; } public void setAuthenticationMethod(final String authenticationMethod) { this.authenticationMethod = authenticationMethod; } public String getAttributeName() { return attributeName; } public void setAttributeName(final String attributeName) { this.attributeName = attributeName; } public String getAttributePattern() { return attributePattern; } public void setAttributePattern(final String attributePattern) { this.attributePattern = attributePattern; } /** * Checks the the class stores valid data. * * @return true if valid data, otherwise false */ public boolean isValid() { if (this.attributeName == null) { logger.debug(CANNOT_BE_NULL, MFA_ATTRIBUTE_NAME); return false; } if (this.attributePattern == null) { logger.debug(CANNOT_BE_NULL, MFA_ATTRIBUTE_PATTERN); return false; } if (this.authenticationMethod == null) { logger.debug(CANNOT_BE_NULL, MultiFactorAuthenticationRequestResolver.DEFAULT_MFA_METHOD_ATTRIBUTE_NAME); return false; } return true; } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } } }